Skip to content

Commit

Permalink
Update quality tools.
Browse files Browse the repository at this point in the history
- Add pyproject-fmt to quality tools.
- Remove safety from quality tools.
- Remove duplication between unittest.sh scripts.
- Remove duplication between quality.sh scripts.
- Remove duplication between pip-compile.sh scripts.
- Remove duplication between pip-install.sh scripts.

Closes #8928.
  • Loading branch information
fniessink committed Jun 17, 2024
1 parent ddce9fe commit ac9badf
Show file tree
Hide file tree
Showing 91 changed files with 1,346 additions and 971 deletions.
19 changes: 15 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
auth:
username: $DOCKERHUB_USERNAME
password: $DOCKERHUB_PASSWORD
parallelism: 5
parallelism: 6
steps:
- checkout
- run: |
Expand All @@ -19,7 +19,8 @@ jobs:
1) component=components/notifier;;
2) component=components/api_server;;
3) component=components/shared_code;;
4) component=tests/feature_tests;;
4) component=tests/application_tests;;
5) component=tests/feature_tests;;
esac
cd $component
mkdir -p build
Expand All @@ -36,6 +37,10 @@ jobs:
path: components/api_server/build
- store_artifacts:
path: components/shared_code/build
- store_artifacts:
path: components/application_tests/build
- store_artifacts:
path: components/feature_tests/build

unittest_frontend:
docker:
Expand All @@ -51,7 +56,7 @@ jobs:
ci/unittest.sh
ci/quality.sh
unittest_docs:
unittest_other:
machine:
image: default
steps:
Expand All @@ -64,6 +69,12 @@ jobs:
ci/pip-install.sh
ci/unittest.sh
ci/quality.sh
- run: |
cd release
python3 -m venv venv
. venv/bin/activate
ci/pip-install.sh
ci/quality.sh
application_tests:
machine:
Expand Down Expand Up @@ -117,7 +128,7 @@ workflows:
context: QualityTime
- unittest_frontend:
context: QualityTime
- unittest_docs:
- unittest_other:
context: QualityTime
- docker/hadolint:
context: QualityTime
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/application-tests-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Application tests quality

on: [push]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/[email protected]
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
cd tests/application_tests
ci/pip-install.sh
- name: Test
run: |
cd tests/application_tests
ci/unittest.sh
- name: Quality
run: |
cd tests/application_tests
ci/quality.sh
23 changes: 23 additions & 0 deletions .github/workflows/release-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Release script quality

on: [push]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/[email protected]
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
cd release
ci/pip-install.sh
- name: Quality
run: |
cd release
ci/quality.sh
20 changes: 2 additions & 18 deletions ci/base.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,13 @@

set -e

run () {
run() {
# Show the invoked command using a subdued text color so it's clear which tool is running.
header='\033[95m'
endstyle='\033[0m'
echo -e "${header}$*${endstyle}"
eval "$*"
}

spec () {
# The versions of tools are specified in pyproject.toml. This function calls the spec.py script which in turn
# reads the version numbers from the pyproject.toml file.

# Get the dir of this script so the spec.py script that is in the same dir as this script can be invoked:
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
python $SCRIPT_DIR/spec.py $*
}

# Don't install tools in the global pipx home folder, but locally for each component:
export PIPX_HOME=.pipx
export PIPX_BIN_DIR=$PIPX_HOME/bin

# For Windows compatibility; prevent path from ending with a ':'
export PYTHONPATH=`python -c 'import sys;print(":".join(sys.argv[1:]))' src $PYTHONPATH`

# Insert a custom compile command in generated requirements file, so it's clear how they are generated:
export CUSTOM_COMPILE_COMMAND="ci/pip-compile.sh"
export PYTHONPATH=$(python -c 'import sys;print(":".join(sys.argv[1:]))' src $PYTHONPATH)
17 changes: 17 additions & 0 deletions ci/pip-base.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

source base.sh

# Insert a custom compile command in generated requirements file, so it's clear how they are generated:
export CUSTOM_COMPILE_COMMAND="ci/pip-compile.sh"

run_pip_compile() {
if [[ -f "requirements/requirements.txt" ]]; then
run pip-compile --output-file requirements/requirements.txt pyproject.toml
fi
run pip-compile --extra dev --output-file requirements/requirements-dev.txt pyproject.toml
}

run_pip_install() {
run pip install --ignore-installed --quiet --use-pep517 $@
}
26 changes: 26 additions & 0 deletions ci/pipx-base.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash

source base.sh

script_dir() {
# Get the dir of this script so that scripts that are in the same dir as this script can be invoked
# See https://stackoverflow.com/questions/39340169/dir-cd-dirname-bash-source0-pwd-how-does-that-work
echo $( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
}

spec() {
# The versions of tools are specified in pyproject.toml. This function calls the spec.py script which in turn
# reads the version numbers from the pyproject.toml file.
python $(script_dir)/spec.py $*
}

run_pipx() {
# Look up the version of the command using the spec function and run the command using pipx.
command_spec=$(spec $1)
shift 1
run pipx run $command_spec $@
}

# Don't install tools in the global pipx home folder, but locally for each component:
export PIPX_HOME=.pipx
export PIPX_BIN_DIR=$PIPX_HOME/bin
13 changes: 13 additions & 0 deletions ci/python_files_and_folders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Determine the Python files and folders in the current directory."""

from pathlib import Path

def python_files_and_folders() -> list[str]:
"""Return the Python files and folders in the current directory."""
python_files = [python_file.name for python_file in Path(".").glob('*.py') if not python_file.name.startswith(".")]
python_folders = [folder_name for folder_name in ("src", "tests") if Path(folder_name).exists()]
return python_files + python_folders


if __name__ == "__main__":
print(" ".join(python_files_and_folders()))
61 changes: 61 additions & 0 deletions ci/quality-base.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/bin/bash

source pipx-base.sh

PYTHON_FILES_AND_FOLDERS=$(python $(script_dir)/python_files_and_folders.py)

run_ruff() {
run_pipx ruff check $PYTHON_FILES_AND_FOLDERS
run_pipx ruff format --check $PYTHON_FILES_AND_FOLDERS
}

run_fixit() {
run_pipx fixit lint $PYTHON_FILES_AND_FOLDERS
}

run_mypy() {
pydantic_spec=$(spec pydantic)
if [[ "$pydantic_spec" == "" ]]; then
run_pipx mypy --python-executable=$(which python) $PYTHON_FILES_AND_FOLDERS
else
# To use the pydantic plugin, we need to first install mypy and then inject pydantic
run pipx install --force $(spec mypy) # --force works around this bug: https://github.com/pypa/pipx/issues/795
run pipx inject mypy $pydantic_spec
run $PIPX_BIN_DIR/mypy --python-executable=$(which python) $PYTHON_FILES_AND_FOLDERS
fi
}

run_pyproject_fmt() {
run_pipx pyproject-fmt --check pyproject.toml
}

run_bandit() {
run_pipx bandit --configfile pyproject.toml --quiet --recursive $PYTHON_FILES_AND_FOLDERS
}

run_pip_audit() {
run_pipx pip-audit --strict --progress-spinner=off $(python $(script_dir)/requirements_files.py)
}

run_vulture() {
run_pipx vulture --min-confidence 0 $PYTHON_FILES_AND_FOLDERS .vulture_ignore_list.py $@
}

run_vale() {
run_pipx vale sync
run_pipx vale --no-wrap src/*.md
}

run_markdownlint() {
run ./node_modules/markdownlint-cli/markdownlint.js src/*.md
}

check_python_quality() {
run_ruff
run_fixit
run_mypy
run_pyproject_fmt
run_pip_audit
run_bandit
run_vulture
}
13 changes: 13 additions & 0 deletions ci/requirements_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Determine the Python requirements files."""

from pathlib import Path

def requirements_files() -> list[str]:
"""Return the Python requirements files in the requirements directory."""
# We never return the internal requirements file, because it does not need to be checked
requirements_filenames = ("requirements/requirements.txt", "requirements/requirements-dev.txt")
return [f"-r {filename}" for filename in requirements_filenames if Path(filename).exists()]


if __name__ == "__main__":
print(" ".join(requirements_files()))
3 changes: 2 additions & 1 deletion ci/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ def spec(package: str, pyproject_toml_path: Path) -> str:
with pyproject_toml_path.open("rb") as pyproject_toml_file:
pyproject_toml = tomllib.load(pyproject_toml_file)
tools = pyproject_toml["project"]["optional-dependencies"]["tools"]
return [spec for spec in tools if spec.split("==")[0] == package][0]
package_specs = [spec for spec in tools if spec.split("==")[0] == package]
return package_specs[0] if package_specs else ""


if __name__ == "__main__":
Expand Down
11 changes: 8 additions & 3 deletions ci/unittest-base.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
#!/bin/bash

# Get the dir of this script so the vbase.sh script that is in the same dir as this script can be sourced:
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source $SCRIPT_DIR/base.sh
source base.sh

# Turn on development mode, see https://docs.python.org/3/library/devmode.html
export PYTHONDEVMODE=1

run_coverage() {
run coverage run -m unittest --quiet
run coverage report --fail-under=0
run coverage html --fail-under=0
run coverage xml # Fail if coverage is too low, but only after the text and HTML reports have been generated
}
7 changes: 3 additions & 4 deletions components/api_server/ci/pip-compile.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/bin/bash

source ../../ci/base.sh
PATH="$PATH:../../ci"
source pip-base.sh

# Update the compiled requirements files
run pip-compile --output-file requirements/requirements.txt pyproject.toml
run pip-compile --extra dev --output-file requirements/requirements-dev.txt pyproject.toml
run_pip_compile
8 changes: 4 additions & 4 deletions components/api_server/ci/pip-install.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash

source ../../ci/base.sh
PATH="$PATH:../../ci"
source pip-base.sh

# Install the requirements
run pip install --ignore-installed --quiet --use-pep517 -r requirements/requirements-dev.txt
run pip install --ignore-installed --quiet --use-pep517 -r requirements/requirements-internal.txt
run_pip_install -r requirements/requirements-dev.txt
run_pip_install -r requirements/requirements-internal.txt
31 changes: 3 additions & 28 deletions components/api_server/ci/quality.sh
Original file line number Diff line number Diff line change
@@ -1,31 +1,6 @@
#!/bin/bash

source ../../ci/base.sh
PATH="$PATH:../../ci"
source quality-base.sh

# Ruff
run pipx run `spec ruff` check .
run pipx run `spec ruff` format --check .

# Fixit
run pipx run `spec fixit` lint src tests

# Mypy
run pipx run `spec mypy` --python-executable=$(which python) src

# pip-audit
run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt

# Safety
# Vulnerability ID: 67599
# ADVISORY: ** DISPUTED ** An issue was discovered in pip (all versions) because it installs the version with the
# highest version number, even if the user had intended to obtain a private package from a private index. This only
# affects use of the --extra-index-url option, and exploitation requires that the...
# CVE-2018-20225
# For more information about this vulnerability, visit https://data.safetycli.com/v/67599/97c
run pipx run `spec safety` check --bare --ignore 67599 -r requirements/requirements.txt -r requirements/requirements-dev.txt

# Bandit
run pipx run `spec bandit` --quiet --recursive src/

# Vulture
run pipx run `spec vulture` --min-confidence 0 src/ tests/ .vulture_ignore_list.py
check_python_quality
9 changes: 3 additions & 6 deletions components/api_server/ci/unittest.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#!/bin/bash

source ../../ci/unittest-base.sh
PATH="$PATH:../../ci"
source unittest-base.sh

export COVERAGE_RCFILE=../../.coveragerc

coverage run -m unittest --quiet
coverage report --fail-under=0
coverage html --fail-under=0
coverage xml # Fail if coverage is too low, but only after the text and HTML reports have been generated
run_coverage
Loading

0 comments on commit ac9badf

Please sign in to comment.