Tool Coverage by Ruff¶
Understanding Ruff¶
Ruff is a fast and efficient Python linter written in Rust. It aims to provide comprehensive linting capabilities by integrating multiple linting functionalities into a single tool. Ruff can handle many tasks traditionally managed by separate tools, offering both speed and versatility.
Key Features of Ruff:¶
Linting: Detects syntax errors, code smells, and stylistic issues.
Import Sorting: Manages and sorts imports, effectively replacing tools like
isort
.Fixes: Automatically fixes certain linting issues.
Performance: Extremely fast, making it suitable for large codebases.
Tools You Can Remove When Using Ruff¶
1. isort
¶
Purpose of
isort
: Specifically sorts and organizes import statements in Python files.Ruff’s Replacement Capability: Ruff includes built-in support for import sorting, effectively handling the tasks performed by
isort
.Action: Remove
isort
from your Makefile.## format: Format the code with Black and docformatter. format: black docformatter
(Removed
isort
from theformat
target)
2. pylint
(Optional)¶
Purpose of
pylint
: A comprehensive linter that checks for errors, enforces a coding standard, and looks for code smells.Ruff’s Replacement Capability: Ruff covers a substantial portion of
pylint
’s functionality with improved performance. However,pylint
offers some advanced checks and reporting that Ruff may not cover entirely.Decision:
If Ruff Meets Your Needs: Remove
pylint
for a simpler setup.If You Require Advanced Checks: Keep
pylint
alongside Ruff.
## lint: Lint the code with ruff, Bandit, vulture, Pylint and Mypy. lint: ruff bandit vulture mypy
(Optionally remove
pylint
if not needed)
3. docformatter
¶
Purpose of
docformatter
: Formats docstrings to follow PEP 257 standards.Ruff’s Replacement Capability: Ruff does not handle docstring formatting.
Action: Keep
docformatter
in your Makefile.
4. isort
Replacement Confirmed¶
As mentioned, Ruff replaces
isort
. No additional action needed beyond removal.
Tools to Retain When Using Ruff¶
1. bandit
¶
Purpose: Identifies common security issues in Python code.
Ruff’s Capability: Ruff does not focus on security analysis.
Action: Keep
bandit
for security linting.
2. vulture
¶
Purpose: Detects dead code in Python projects.
Ruff’s Capability: Ruff does not specialize in dead code detection.
Action: Keep
vulture
for identifying unused code.
3. black
¶
Purpose: A code formatter that enforces a consistent coding style.
Ruff’s Capability: While Ruff can fix some formatting issues, it does not fully replace
black
.Action: Keep
black
for comprehensive code formatting.
4. mypy
¶
Purpose: Performs static type checking in Python.
Ruff’s Capability: Ruff does not perform type checking.
Action: Keep
mypy
for type safety and static analysis.
5. coveralls
¶
Purpose: Uploads coverage data to Coveralls.io for test coverage reporting.
Ruff’s Capability: Ruff does not handle test coverage.
Action: Keep
coveralls
for coverage reporting.
6. sphinx
¶
Purpose: Generates documentation from source code.
Ruff’s Capability: Ruff does not handle documentation generation.
Action: Keep
sphinx
for creating and managing documentation.
7. conda
Environments¶
Purpose: Manages project dependencies and environments.
Ruff’s Capability: Ruff does not manage environments.
Action: Keep
conda
for environment management.
8. docker
, compileall
, next-version
, etc.¶
Purpose: Various tasks like building Docker images, compiling Python scripts, and version management.
Ruff’s Capability: Ruff does not handle these tasks.
Action: Keep these tools as needed for your project workflow.
Revised Makefile Example¶
Based on the above analysis, here’s how you can adjust your Makefile to remove unnecessary tools when using Ruff:
.DEFAULT_GOAL := help
MODULE=iotemplateapp
PYTHONPATH=${MODULE} docs scripts tests
ARCH:=$(shell uname -m)
OS:=$(shell uname -s)
ifeq (${OS},Linux)
DOCKER2EXE_DIR=linux-amd64
DOCKER2EXE_SCRIPT=sh
DOCKER2EXE_TARGET=linux/amd64
else ifeq (${OS},Darwin)
DOCKER2EXE_SCRIPT=zsh
ifeq ($(ARCH),arm64)
DOCKER2EXE_DIR=darwin-arm64
DOCKER2EXE_TARGET=darwin/arm64
else ifeq ($(ARCH),x86_64)
DOCKER2EXE_DIR=darwin-amd64
DOCKER2EXE_TARGET=darwin/amd64
endif
endif
export ENV_FOR_DYNACONF=test
export LANG=en_US.UTF-8
## =============================================================================
## make Script The purpose of this Makefile is to support the whole
## software development process for an application. It
## contains also the necessary tools for the CI activities.
## -----------------------------------------------------------
## The available make commands are:
## -----------------------------------------------------------------------------
## help: Show this help.
## -----------------------------------------------------------------------------
## action: Run the GitHub Actions locally.
action: action-std
## dev: Format, lint and test the code.
dev: format lint tests
## docs: Check the API documentation, create and upload the user documentation.
docs: sphinx
## everything: Do everything precheckin
everything: dev docs
## final: Format, lint and test the code and create the documentation.
final: format lint docs tests
## format: Format the code with Black.
format: black docformatter
## lint: Lint the code with ruff, Bandit, vulture, and Mypy.
lint: ruff bandit vulture mypy
## pre-push: Preparatory work for the pushing process.
pre-push: format lint tests next-version docs
## tests: Run all tests with pytest.
tests: pytest
## -----------------------------------------------------------------------------
help:
@sed -ne '/@sed/!s/## //p' ${MAKEFILE_LIST}
# Run the GitHub Actions locally.
# https://github.com/nektos/act
# Configuration files: .act_secrets & .act_vars
action-std: ## Run the GitHub Actions locally: standard.
@echo "Info ********** Start: action ***************************************"
@echo "Copy your .aws/credentials to .aws_secrets"
@echo "----------------------------------------------------------------------"
act --version
@echo "----------------------------------------------------------------------"
act --quiet \
--secret-file .act_secrets \
--var IO_LOCAL='true' \
--verbose \
-P ubuntu-latest=catthehacker/ubuntu:act-latest \
-W .github/workflows/github_pages.yml
act --quiet \
--secret-file .act_secrets \
--var IO_LOCAL='true' \
--verbose \
-P ubuntu-latest=catthehacker/ubuntu:act-latest \
-W .github/workflows/standard.yml
@echo "Info ********** End: action ***************************************"
# Bandit is a tool designed to find common security issues in Python code.
# https://github.com/PyCQA/bandit
# Configuration file: none
bandit: ## Find common security issues with Bandit.
@echo "Info ********** Start: Bandit ***************************************"
@echo "PYTHONPATH=${PYTHONPATH}"
@echo "----------------------------------------------------------------------"
bandit --version
@echo "----------------------------------------------------------------------"
bandit -c pyproject.toml -r ${PYTHONPATH}
@echo "Info ********** End: Bandit ***************************************"
# The Uncompromising Code Formatter
# https://github.com/psf/black
# Configuration file: pyproject.toml
black: ## Format the code with Black.
@echo "Info ********** Start: black ****************************************"
@echo "PYTHONPATH=${PYTHONPATH}"
@echo "----------------------------------------------------------------------"
black --version
@echo "----------------------------------------------------------------------"
black ${PYTHONPATH}
@echo "Info ********** End: black ****************************************"
# Byte-compile Python libraries
# https://docs.python.org/3/library/compileall.html
# Configuration file: none
compileall: ## Byte-compile the Python libraries.
@echo "Info ********** Start: Compile All Python Scripts *******************"
python3 --version
@echo "----------------------------------------------------------------------"
python3 -m compileall
@echo "Info ********** End: Compile All Python Scripts *******************"
# Miniconda - Minimal installer for conda.
# https://docs.conda.io/en/latest/miniconda.html
# Configuration file: none
conda-dev: ## Create a new environment for development.
@echo "Info ********** Start: Miniconda create development environment *****"
conda config --set always_yes true
conda --version
echo "PYPI_PAT=${PYPI_PAT}"
@echo "----------------------------------------------------------------------"
conda env remove -n ${MODULE} 2>/dev/null || echo "Environment '${MODULE}' does not exist."
conda env create -f config/environment_dev.yml
@echo "----------------------------------------------------------------------"
conda info --envs
conda list
@echo "Info ********** End: Miniconda create development environment *****"
conda-prod: ## Create a new environment for production.
@echo "Info ********** Start: Miniconda create production environment ******"
conda config --set always_yes true
conda --version
@echo "----------------------------------------------------------------------"
conda env remove -n ${MODULE} 2>/dev/null || echo "Environment '${MODULE}' does not exist."
conda env create -f config/environment.yml
@echo "----------------------------------------------------------------------"
conda info --envs
conda list
@echo "Info ********** End: Miniconda create production environment ******"
# Requires a public repository !!!
# Python interface to coveralls.io API
# https://github.com/TheKevJames/coveralls-python
# Configuration file: none
coveralls: ## Run all the tests and upload the coverage data to coveralls.
@echo "Info ********** Start: coveralls ************************************"
pytest --cov=${MODULE} --cov-report=xml --random-order tests
@echo "----------------------------------------------------------------------"
coveralls --service=github
@echo "Info ********** End: coveralls ************************************"
# Formats docstrings to follow PEP 257
# https://github.com/PyCQA/docformatter
# Configuration file: pyproject.toml
docformatter: ## Format the docstrings with docformatter.
@echo "Info ********** Start: docformatter *********************************"
@echo "PYTHONPATH=${PYTHONPATH}"
@echo "----------------------------------------------------------------------"
docformatter --version
@echo "----------------------------------------------------------------------"
docformatter --in-place -r ${PYTHONPATH}
# docformatter -r ${PYTHONPATH}
@echo "Info ********** End: docformatter *********************************"
# Creates Docker executables
# https://github.com/rzane/docker2exe
# Configuration files: .dockerignore & Dockerfile
docker: ## Create a docker image.
@echo "Info ********** Start: Docker ***************************************"
@echo "OS =${OS}"
@echo "ARCH =${ARCH}"
@echo "DOCKER2EXE_DIR =${DOCKER2EXE_DIR}"
@echo "DOCKER2EXE_SCRIPT=${DOCKER2EXE_SCRIPT}"
@echo "DOCKER2EXE_TARGET=${DOCKER2EXE_TARGET}"
@echo "----------------------------------------------------------------------"
docker ps -a
@echo "----------------------------------------------------------------------"
@sh -c 'docker ps -a | grep -q "${MODULE}" && docker rm --force ${MODULE} || echo "No existing container to remove."'
@sh -c 'docker image ls | grep -q "${MODULE}" && docker rmi --force ${MODULE}:latest || echo "No existing image to remove."'
docker system prune -a -f
docker build --build-arg PYPI_PAT=${PYPI_PAT} -t ${MODULE} .
@echo "----------------------------------------------------------------------"
rm -rf app-${DOCKER2EXE_DIR}
mkdir app-${DOCKER2EXE_DIR}
chmod +x dist/docker2exe-${DOCKER2EXE_DIR}
./dist/docker2exe-${DOCKER2EXE_DIR} --name ${MODULE} \
--image ${MODULE}:latest \
--embed \
-t ${DOCKER2EXE_TARGET} \
-v ./data:/app/data \
-v ./logging_cfg.yaml:/app/logging_cfg.yaml \
-v ./settings.io_aero.toml:/app/settings.io_aero.toml
mkdir app-${DOCKER2EXE_DIR}/data
mv dist/${MODULE}-${DOCKER2EXE_DIR} app-${DOCKER2EXE_DIR}/${MODULE}
chmod +x app-${DOCKER2EXE_DIR}/${MODULE}
cp logging_cfg.yaml app-${DOCKER2EXE_DIR}/
cp run_iotemplateapp.${DOCKER2EXE_SCRIPT} app-${DOCKER2EXE_DIR}/
chmod +x app-${DOCKER2EXE_DIR}/*.${DOCKER2EXE_SCRIPT}
cp settings.io_aero.toml app-${DOCKER2EXE_DIR}/
@echo "Info ********** End: Docker ***************************************"
# isort your imports, so you don't have to.
# https://github.com/PyCQA/isort
# Configuration file: pyproject.toml
# Removed `isort` as Ruff handles import sorting.
# Mypy: Static Typing for Python
# https://github.com/python/mypy
# Configuration file: pyproject.toml
mypy: ## Find typing issues with Mypy.
@echo "Info ********** Start: Mypy *****************************************"
@echo "PYTHONPATH=${PYTHONPATH}"
@echo "----------------------------------------------------------------------"
mypy --version
@echo "----------------------------------------------------------------------"
mypy ${PYTHONPATH}
@echo "Info ********** End: Mypy *****************************************"
mypy-stubgen: ## Autogenerate stub files.
@echo "Info ********** Start: Mypy *****************************************"
@echo "MODULE=${MODULE}"
@echo "----------------------------------------------------------------------"
rm -rf out
stubgen --package ${MODULE}
cp -f out/${MODULE}/* ./${MODULE}/
rm -rf out
@echo "Info ********** End: Mypy *****************************************"
next-version: ## Increment the version number.
@echo "Info ********** Start: next_version *********************************"
@echo "PYTHONPATH=${PYTHONPATH}"
@echo "----------------------------------------------------------------------"
python3 --version
@echo "----------------------------------------------------------------------"
python3 scripts/next_version.py
@echo "Info ********** End: next version *********************************"
# Pylint is a tool that checks for errors in Python code.
# https://github.com/PyCQA/pylint/
# Configuration file: .pylintrc
# Removed `pylint` if not needed; otherwise keep it.
# pytest: helps you write better programs.
# https://github.com/pytest-dev/pytest/
# Configuration file: pyproject.toml
pytest: ## Run all tests with pytest.
@echo "Info ********** Start: pytest ***************************************"
@echo "CONDA =${CONDA_PREFIX}"
@echo "PYTHONPATH=${PYTHONPATH}"
@echo "----------------------------------------------------------------------"
pytest --version
@echo "----------------------------------------------------------------------"
pytest --dead-fixtures tests
pytest --cache-clear --cov=${MODULE} --cov-report term-missing:skip-covered --cov-report=lcov -v tests
@echo "Info ********** End: pytest ***************************************"
pytest-ci: ## Run all tests with pytest after test tool installation.
@echo "Info ********** Start: pytest ***************************************"
@echo "CONDA =${CONDA_PREFIX}"
@echo "PYTHONPATH=${PYTHONPATH}"
@echo "----------------------------------------------------------------------"
pip3 install pytest pytest-cov pytest-deadfixtures pytest-helpers-namespace pytest-random-order
@echo "----------------------------------------------------------------------"
pytest --version
@echo "----------------------------------------------------------------------"
pytest --dead-fixtures tests
pytest --cache-clear --cov=${MODULE} --cov-report term-missing:skip-covered -v tests
@echo "Info ********** End: pytest ***************************************"
pytest-first-issue: ## Run all tests with pytest until the first issue occurs.
@echo "Info ********** Start: pytest ***************************************"
@echo "CONDA =${CONDA_PREFIX}"
@echo "PYTHONPATH=${PYTHONPATH}"
@echo "----------------------------------------------------------------------"
pytest --version
@echo "----------------------------------------------------------------------"
pytest --cache-clear --cov=${MODULE} --cov-report term-missing:skip-covered -rP -v -x tests
@echo "Info ********** End: pytest ***************************************"
pytest-ignore-mark: ## Run all tests without marker with pytest."
@echo "Info ********** Start: pytest ***************************************"
@echo "CONDA =${CONDA_PREFIX}"
@echo "PYTHONPATH=${PYTHONPATH}"
@echo "----------------------------------------------------------------------"
pytest --version
@echo "----------------------------------------------------------------------"
pytest --dead-fixtures -m "not no_ci" tests
pytest --cache-clear --cov=${MODULE} --cov-report term-missing:skip-covered --cov-report=lcov -m "not no_ci" -v tests
@echo Info ********** End: pytest ***************************************
pytest-issue: ## Run only the tests with pytest which are marked with 'issue'.
@echo Info ********** Start: pytest ***************************************
@echo CONDA =${CONDA_PREFIX}
@echo PYTHONPATH=${PYTHONPATH}
@echo "----------------------------------------------------------------------"
pytest --version
@echo "----------------------------------------------------------------------"
pytest --dead-fixtures tests
pytest --cache-clear --capture=no --cov=${MODULE} --cov-report term-missing:skip-covered -m issue -rP -v -x tests
@echo "Info ********** End: pytest ***************************************"
pytest-module: ## Run test of a specific module with pytest.
@echo "Info ********** Start: pytest ***************************************"
@echo "CONDA =${CONDA_PREFIX}"
@echo "PYTHONPATH=${PYTHONPATH}"
@echo "TESTMODULE=tests/${TEST-MODULE}.py"
@echo "----------------------------------------------------------------------"
pytest --version
@echo "----------------------------------------------------------------------"
pytest --cache-clear --cov=${MODULE} --cov-report term-missing:skip-covered -v tests/${TEST-MODULE}.py
@echo "Info ********** End: pytest ***************************************"
# https://github.com/astral-sh/ruff
# Configuration file: pyproject.toml
ruff: ## An extremely fast Python linter and code formatter.
@echo "Info ********** Start: ruff *****************************************"
ruff --version
@echo "----------------------------------------------------------------------"
ruff check --fix
@echo "Info ********** End: ruff *****************************************"
sphinx: ## Create the user documentation with Sphinx.
@echo "Info ********** Start: sphinx ***************************************"
sphinx-apidoc --version
sphinx-build --version
@echo "----------------------------------------------------------------------"
sudo rm -rf docs/build/*
sphinx-apidoc -o docs/source ${MODULE}
sphinx-build -M html docs/source docs/build
sphinx-build -b rinoh docs/source docs/build/pdf
@echo "Info ********** End: sphinx ***************************************"
version: ## Show the installed software versions.
@echo "Info ********** Start: version **************************************"
python3 --version
pip3 --version
@echo "Info ********** End: version **************************************"
# Find dead Python code
# https://github.com/jendrikseipp/vulture
# Configuration file: pyproject.toml
vulture: ## Find dead Python code.
@echo "Info ********** Start: vulture **************************************"
@echo "PYTHONPATH=${PYTHONPATH}"
@echo "----------------------------------------------------------------------"
vulture --version
@echo "----------------------------------------------------------------------"
vulture ${PYTHONPATH}
@echo "Info ********** End: vulture **************************************"
## =============================================================================
Key Changes:¶
Removed
isort
:Before:
## format: Format the code with Black and docformatter. format: isort black docformatter
After:
## format: Format the code with Black. format: black docformatter
Reason: Ruff handles import sorting, making
isort
redundant.
Optional Removal of
pylint
:Before:
lint: ruff bandit vulture pylint mypy
After (if removing
pylint
):lint: ruff bandit vulture mypy
Reason: Ruff covers many linting tasks previously handled by
pylint
. However, evaluate if Ruff’s linting suffices for your project’s needs before removingpylint
.
Adjusted
format
Target:Removed
isort
from theformat
target as Ruff handles import sorting.
Other Considerations:¶
docformatter
is retained because Ruff does not handle docstring formatting.Security (
bandit
), dead code detection (vulture
), type checking (mypy
), and documentation (sphinx
) remain essential and should be kept.black
remains for comprehensive code formatting that Ruff does not fully replace.
Final Recommendations¶
Evaluate Ruff’s Coverage:
Before removing tools like
pylint
, ensure that Ruff’s linting capabilities meet your project’s requirements. Run Ruff extensively to verify that it catches all necessary issues.
Maintain Essential Tools:
Security: Keep
bandit
to ensure your code remains secure.Dead Code Detection: Retain
vulture
to identify and remove unused code.Type Checking: Continue using
mypy
for type safety.Documentation: Keep
sphinx
anddocformatter
for maintaining and formatting documentation.
Simplify Your Workflow:
Removing redundant tools like
isort
can streamline your Makefile and reduce maintenance overhead.Ensure that the remaining tools are well-integrated and complement each other to maintain code quality and consistency.
Continuous Integration (CI):
Update your CI pipelines to reflect the changes in the Makefile, ensuring that only the necessary tools are invoked during automated checks.
Documentation and Team Alignment:
Document the changes to the Makefile and inform your team to ensure everyone is aware of the new setup.
Provide guidelines on how to use Ruff alongside the retained tools for optimal code quality.