Python - Code Quality tools
Elevate your Python code quality! Discover 9 essential static analysis tools like Pylint, Black, and mypy, plus an optimal QA workflow to ensure cleaner, bug-free code.
This article is part of the code quality series:
- Markdown linter - solve formatting issue in md files
- Rector 1.0.0 🎉🎉🎉, my friend, my coach
- Docker image that provides static analysis tools for PHP
- Clean code - Linux Bash - Keep the number of function parameters as small as possible
- SQL - Formatting tool
- JSON - Online linter
- Format poorly formatted PHP code
- Python - Code Quality tools
- Git - pre-commit-hooks
- Validate your OpenAPI schema against the Belgif REST standards
Changelog

This article catalogs the author's Python code-quality toolchain — Pylint, Autoflake, isort, Vulture, pydocstyle, mypy, Pyright, Black, and Prospector, plus Ruff as an emerging fast all-in-one alternative — with the exact CLI command and config file for each. They're chained together, fastest/most fundamental first, in a make qa target that stops at the first failure and only prints "CONGRATULATIONS" when every check passes.
If you're a self-respecting programmer, you can't develop without code analysis tools. For PHP programmers, you already know a lot of them (rector, phpstan, phan, phpcs, ...). See my previous articles Rector 1.0.0, my friend, my coach and Docker image that provides static analysis tools for PHP.
What about Python?
The Result: One Command, Nine Checks, One Clear Failure Point
make qa chains Pylint, Autoflake, isort, Vulture, pydocstyle, mypy, Pyright, Black and Prospector — fastest tool first — and stops at the very first failure. Here's what a clean run looks like:
Nothing green to look for: silence (or a rating like Pylint's 10.00/10) means the check passed. Miss a single one and the chain stops right there — no CONGRATULATIONS, and only one thing to fix before re-running.
Why It Works
- Tools run fastest/most fundamental first: no point running a slow type-checker if the code doesn't even parse — that's why Pylint is 1/9 and Autoflake, cleaning up imports, is 2/9.
- The chain stops at the first failure: one
make qarun, one clear thing to fix, not nine reports to reconcile. make qais a single entry point: the same target is what you run by hand, what a pre-commit hook runs, and what CI runs — more on that in the conclusion.
Installation
I'm a big fan of static code quality tools and here is my short list, wired together with a makefile action called qa:
# cspell:disable.PHONY: qaqa: @printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " Step Tool" "Run?" "Techno" "Description" @echo "--------------------------------------------------------------------------------------------------------------" @printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 1/9 RUNNING make Pylint" "Lint python scripts using Pylint (https://pypi.org/project/pylint/)" @pylint . --rcfile .config/.pylintrc
@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 2/9 RUNNING make Autoflake" "Detect unused variables and unused imports (https://pypi.org/project/autoflake/)" @autoflake --remove-unused-variables --remove-all-unused-import --recursive .
@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 3/9 RUNNING make isort" "Sort import statement using isort (https://pycqa.github.io/isort/)" @isort .
@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 4/9 RUNNING make Vulture" "Find dead Python code (https://github.com/jendrikseipp/vulture)" @vulture --min-confidence 100 .
@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 5/9 RUNNING make Pydocstyle" "pydocstyle is a static analysis tool for checking compliance with Python docstring conventions (https://www.pydocstyle.org/en/stable/)" @pydocstyle --config=.config/.pydocstyle
@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 6/9 RUNNING make Mypy" "Mypy is a program that will type check your Python code (https://github.com/python/mypy/)" @mypy --config-file .config/.mypy.ini .
@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 7/9 RUNNING make Pyright" "Pyright is a full-featured, standards-based static type checker for Python (https://github.com/microsoft/pyright)" @pyright --project .config/pyright.json
@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 8/9 RUNNING make Black" "Format the script using Black (https://black.readthedocs.io/en/stable/)" @black --config .config/black.toml .
@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n\n" " 9/9 RUNNING make Prospector" "Inspects Python source files and provides information about type and location of classes, methods, ... (https://github.com/prospector-dev/prospector/)" @prospector . --profile .config/prospector.yaml --pylint-config-file .config/.pylintrc
@printf "%s\n" "CONGRATULATIONS!!!" @printf "%s\n" "Not the slightest error or notice detected, that's .. amazing!" @printf "%s\n" "🥳 🎉 🎊 🤩 🕺 💃 👏"This Makefile defines a 'qa' target that runs a sequence of nine Python code quality and analysis tools in order, printing a formatted table header and status messages for each step. Each tool checks different aspects like code style, unused variables, type correctness, and docstring compliance, culminating in a congratulations message if all checks pass.
As soon as an error is detected, the script stops. You'll see the CONGRATULATIONS message, exactly as shown above, only if all nine checks are successful.
More Demos: The Nine Tools, One by One
1. Pylint
https://pypi.org/project/pylint/
Pylint is a static code analyzer for Python 2 or 3. The latest version supports Python 3.9.0 and above.
Pylint analyzes your code without actually running it. It checks for errors, enforces a coding standard, looks for code-smells, and can make suggestions about how the code could be refactored.
First things first: make sure your Python code has no syntax errors — no bad indentation, no forgotten : at the end of a control statement (like an if or for).
I'm running it like this: pylint . --rcfile .config/.pylintrc.
[MASTER]; Pickle collected data for later comparisons.persistent=yes
; List of plugins (as comma-separated values of python modules names) to load,; usually to register additional checkers.load-plugins= pylint.extensions.check_elif, pylint.extensions.bad_builtin, pylint.extensions.docparams, pylint.extensions.for_any_all, pylint.extensions.set_membership, pylint.extensions.code_style, pylint.extensions.overlapping_exceptions, pylint.extensions.typing, pylint.extensions.redefined_variable_type, pylint.extensions.comparison_placement,
; Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the; number of processors available to use.jobs=0
; When enabled, pylint would attempt to guess common misconfiguration and emit; user-friendly hints instead of false-positive error messages.suggestion-mode=yes
; Minimum supported python versionpy-version = 3.13.0
; Specify a score threshold to be exceeded before program exits with error.fail-under=9.50
[FORMAT]max-line-length=120This is a configuration file for Pylint, a Python code quality checker. It sets up various options like saving analysis results for comparison, loading extra checker plugins to catch more code issues, using all available processors for speed, enabling helpful suggestions, targeting Python 3.13, and defining thresholds for when the checker should report failure.
2. Autoflake
https://pypi.org/project/autoflake/
Autoflake removes unused imports and unused variables from Python code. It makes use of pyflakes to do this.
By default, Autoflake only removes unused imports for modules that are part of the standard library. (Other modules may have side effects that make them unsafe to remove automatically.) Removal of unused variables is also disabled by default.
Autoflake also removes useless pass statements by default.
I'm running it like this: autoflake --remove-unused-variables --remove-all-unused-import --recursive .
3. isort
https://pycqa.github.io/isort/
isortyour imports, so you don't have to.isort is a Python utility / library to sort imports alphabetically, and automatically separates into sections and by type. It provides a command line utility, Python library and plugins for various editors to quickly sort all your imports. It requires Python 3.7+ to run but supports formatting Python 2 code too.
I'm running it like this: isort .
Note: I've also configured my VSCode with this setting "python.sortImports.args": ["--profile", "black"] so import statements are automatically sorted while I'm coding.
4. vulture
https://github.com/jendrikseipp/vulture
Vulture finds unused code in Python programs. This is useful for cleaning up and finding errors in large code bases. If you run Vulture on both your library and test suite you can find untested code.
Due to Python's dynamic nature, static code analyzers like Vulture are likely to miss some dead code. Also, code that is only called implicitly may be reported as unused. Nonetheless, Vulture can be a very helpful tool for higher code quality.
I'm running it like this: vulture --min-confidence 100 .
Danger
Be careful with Vulture because its algorithm will detect a lot of false positives so this is why, in my automation process, I've used --min-confidence 100 to make sure I've only real unused code / variables.
Note: I've also configured my VSCode with the settings below so, while I'm coding, VSCode will notify me about unused things so I can immediately take action.
"python.analysis.diagnosticSeverityOverrides": { "reportUnusedClass": "information", "reportUnusedFunction": "information", "reportUnusedImport": "information", "reportUnusedVariable": "information"},This configuration object tells a Python code analyzer to report unused code (like classes, functions, imports, and variables) as low-priority 'information' messages rather than warnings or errors. This is useful when you want to be notified about unused code but don't want it to block your work or fail any checks.
5. pydocstyle
https://www.pydocstyle.org/en/stable/
pydocstyleis a static analysis tool for checking compliance with Python docstring conventions.
This tool checks the quality of your comments, such as your function descriptions, and, for example, makes sure that if you have a function with two arguments, your description explains both arguments (there's a check on the name and the type).
I'm running it like this: pydocstyle --config=.config/.pydocstyle
[pydocstyle]ignore = D100,D203,D205,D212,D213,D400,D404,D406,D407,D413,D415match = .*\.pyThis is a configuration file for pydocstyle, a tool that checks whether Python docstrings follow a consistent style. It tells the tool to ignore certain style rules and only check files that end with .py.
Caution
The pydocstyle GitHub repository was archived in November 2023 and is no longer actively maintained. The community recommends migrating to Ruff (see the "Under the Hood" section below), which covers equivalent docstring checks via its pydocstyle-compatible rule set.
6. mypy
https://github.com/python/mypy/
Mypy is a static type checker for Python.
Type checkers help ensure that you're using variables and functions in your code correctly. With mypy, add type hints (PEP 484) to your Python programs, and mypy will warn you when you use those types incorrectly.
Python is a dynamic language, so usually you'll only see errors in your code when you attempt to run it. Mypy is a static checker, so it finds bugs in your programs without even running them!
I'm running it like this: mypy --config-file .config/.mypy.ini .
[mypy]show_error_codes = true
; First we turn on *all the checks*, and then we turn off those that are too annoying.strict = True
cache_dir = /tmp/mypy
no_implicit_optional = true
strict_equality = truewarn_redundant_casts = truewarn_unused_ignores = true
python_version = 3.13This is a mypy configuration file that enables strict type checking for a Python project. It turns on comprehensive type-checking rules to catch potential bugs early, then explicitly keeps them all enabled rather than selectively disabling the strict ones. The project targets Python 3.13 and uses a temporary cache directory.
7. Pyright
https://github.com/microsoft/pyright
Pyright is a full-featured, standards-based static type checker for Python. It is designed for high performance and can be used with large Python source bases.
I'm using it like this: pyright --project .config/pyright.json
{ "include": [ "." ], "pythonVersion": "3.13", "pythonPlatform": "Linux" }This is a Pyrightd configuration file that tells the Python type checker to analyze the current directory, use Python version 3.13, and assume it's running on a Linux platform. These settings ensure consistent type-checking behavior across different environments.
8. Black
https://black.readthedocs.io/en/stable/
By using Black, you agree to cede control over minutiae of hand-formatting. In return, Black gives you speed, determinism, and freedom from pycodestyle nagging about formatting. You will save time and mental energy for more important matters.
Black makes code review faster by producing the smallest diffs possible. Blackened code looks the same regardless of the project you’re reading. Formatting becomes transparent after a while and you can focus on the content instead.
I'm using it like this: black --config .config/black.toml .
[tool.black]
# The same as in prospector.yaml and .pylintrcline-length = 120
# We'll format the code for Python 3.13.0; we can add multiple versions (the ones supported by# the script, all versions separated by a comma like in ['py312', 'py313'] (run "black --help" to get them)target-version = ['py313']This configuration section tells Black, a Python code formatter, to format code with a maximum line length of 120 characters and to use Python 3.13 syntax rules. Black is a tool that automatically reformats Python code to follow consistent style rules across a project.
9. prospector
https://github.com/prospector-dev/prospector/
Inspects Python source files and provides information about type and location of classes, methods etc.
I'm using it like this: prospector . --profile .config/prospector.yaml --pylint-config-file .config/.pylintrc
strictness: hightest-warnings: truedoc-warnings: false
pycodestyle: options: max-line-length: 120
disable: - D100 - D203 - D205 - D212 - D213 - D400 - D404 - D406 - D407 - D413 - D415
bandit: run: true
dodgy: run: true
pyflakes: run: true
mccabe: run: trueThis is a configuration file for a Python code quality checker tool (likely Prospector or a similar linter). It enables strict checking with test warnings, allows lines up to 120 characters long, disables certain documentation style rules that conflict with the project's conventions, and enables several security and complexity analysis tools.
Under the Hood (skip this if you just want to use it)
Extra - Ruff, a potential replacement for several tools
https://github.com/astral-sh/ruff
Ruff, an extremely fast Python linter and code formatter, written in Rust.
Right now, I've too little experience with Ruff so I prefer to add it as extra.
The documentation mentions it is extremely fast and I should admit the first time I've run it I was thinking nothing was done (really too fast). I've made an intentional error in my code and it was detected correctly so, yes, the tool is extremely fast.
According to the Ruff documentation, this tool can completely replace Pylint (the linter), Autoflake (deletion of unused imports/variables), Black (the formatting tool) but does not replace MyPy and Pyright (doc).
I'm using it like this: ruff format --cache-dir /tmp/ruff --config .config/pyproject.toml . and ruff check --cache-dir /tmp/ruff --config .config/pyproject.toml .
[tool.ruff]
exclude=[]
# The same as in prospector.yaml, .pylintrc, black.toml and pyproject.tomlline-length = 120indent-width = 4This is a configuration file for Ruff, a Python linter and code formatter. It sets up Ruff's rules by specifying which files to skip, how long lines can be before triggering warnings, and how many spaces should be used for indentation. These settings ensure consistency across the project's other formatting tools.
Conclusion
Nine tools, one command, one clear failure point — make qa turns "did I remember to check everything" into a single deterministic run, fastest/most fundamental check first, stopping the instant something's wrong.
Running it by hand still relies on you remembering to do it, though. Two ways to make it automatic: Git - pre-commit-hooks refuses the commit when a check fails, and Dagger.io - Using dagger to automate your CI workflows runs the exact same steps locally and in your CI.
This article is part of the code quality series:
- Markdown linter - solve formatting issue in md files
- Rector 1.0.0 🎉🎉🎉, my friend, my coach
- Docker image that provides static analysis tools for PHP
- Clean code - Linux Bash - Keep the number of function parameters as small as possible
- SQL - Formatting tool
- JSON - Online linter
- Format poorly formatted PHP code
- Python - Code Quality tools
- Git - pre-commit-hooks
- Validate your OpenAPI schema against the Belgif REST standards
Loading comments…




