Categories: Coding, Insights, Technology

Jord Piciri

Share

Last year at Process Squad we started preparing for the ISO 9001 certification process, which also includes software quality management. That’s why we decided looking into automatic code quality checks to aid our quality reviews. Since our software is written in Python and we collaborate using GitHub, the solution we are presenting is based on Python and GitHub Actions. It can also be adapted for other programming languages.

First, let’s start by discussing Python coding standards and the tools used to check for them. We based the guidelines on the Python Enhancement Proposals (PEPs) of the Python Software Foundation. These are design documents that describe new features for Python or its processes or environments. PEPs are created by Python developers and enthusiasts to propose new features or changes in to Python’s ecosystem. PEP 8 is one of the most popular PEPs and it is a style guide for Python code. It provides guidelines that improve the readability of code and make it consistent across the wide spectrum of Python code. Consistency within a project is more important than consistency across different projects.

Additionally, we decided to follow PEP 257 and 287 for the code documentation. PEP 257 provides guidelines for writing docstrings that are consistent with the style of Python’s standard library. A docstring is a string literal that occurs as the first statement in a module, function, class, or method definition. It is recommended that all modules should normally have docstrings, and all functions and classes exported by a module should also have docstrings. PEP 287 proposes that the reStructuredText markup be adopted as a standard markup format for structured plaintext documentation in Python docstrings, and for PEPs and ancillary documents as well. reStructuredText is a rich and extensible yet easy-to-read, what-you-see-is-what-you-get plaintext markup syntax.

Next, we needed to find code linters for these guidelines. A code linter is a tool that analyses source code to flag and sometimes fix error, bugs, stylistic issues, and suspicious constructs. It can help you write cleaner code that follows certain standards and best practices. A linter is a basic static code analyzer. To check PEP conformance we use the flake8 tool and some of its plugins. We decided to use the following plugins:

  • pep8-naming: Checks that variable names conform to PEP8.
  • flake8-bandit: Tool designed to find common security issues in Python code.
  • flake8-black: Plugin for black code formatter.
  • flake8-docstrings: Plugin that checks docstrings for PEP257 conformance.
  • flake8-spellcheck: Plugin that spellchecks variables, functions, classes and other bits of your python code in US English.
  • flake8-variables-names: Plugin that helps to make more readable variables names.

We also used code formatters to automatically fix some of the issues listed by flake8. A code formatter is a tool that automatically reformats source code to follow consistent styling guidelines such as indentation, spacing and alignment. These tools are crucial for enhancing the visual appeal and maintainability of source code. Consistent code formatting not only improves the readability of the code but also enhances collaboration among team members. We used the following code formatters:

  • isort: Reformat code and sort imports alphabetically and by standard library, third-party and local.
  • black: Reformat code with black, a PEP8 compliant code formatter.
  • autopep8: Reformat code with autopep8, a PEP8 conformant code formatter.

Now that we selected the tools and plugins, we created a workflow in GitHub Actions to automatically lint and reformat the code. GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your build, test, and deployment pipeline. You can create workflows that build and test every pull request to your repository, or deploy merged pull requests to production. A workflow is a configurable automated process that will run one or more jobs. Workflows are defined by a set of YAML files in your repository. An event is a specific activity in a repository that triggers a workflow run. For example, activity can be a push to a branch or a new issue being opened. A job is a set of steps in a workflow that can run on the same runner.

To begin with, you will need to create the directories .github/workflows in the root directory of your repository and create an empty YAML file there. In the settings of your repository under Code and Automation – Actions – General you need to allow actions and reusable workflows before they can run. We will go through a sample GitHub Actions YAML file step by step:

Select a name for the workflow.

name: CI Pipeline for Code Quality

Trigger workflow on every push to a branch other than ‘main’.

on:

push:

branches-ignore:

- 'main'

Create a job named ‘build’ and run it on a VM with the latest version of Windows Server.

jobs:

build:

runs-on: windows-latest

Step 1: Checkout the repository.

steps:

- name: Check out repository

   uses: actions/checkout@v3

Step 2: Set up the necessary version of Python

- name: Set up Python

   uses: actions/setup-python@v4

   with:

    python-version: '3.11'

Step 3: Install the tools and plugins we described earlier.

- name: Install dependencies

    run: |

python -m pip install --upgrade pip

     pip install --upgrade flake8

     pip install --upgrade isort

     pip install --upgrade pep8-naming

     pip install --upgrade flake8-bandit

     pip install --upgrade flake8-black

     pip install --upgrade flake8-docstrings

     pip install --upgrade flake8-spellcheck

     pip install --upgrade flake8-variables-names

     pip install --upgrade black

     pip install --upgrade autopep8

Step 4: Lint with flake 8 before reformatting the code and save the list of errors into a .TXT file.

You can use optional flags to customize the linting:

  • –exclude: list of files or directories to exclude from checks.
  • –count: Print the total number of errors.
  • –exit-zero: Force Flake8 to use the exit status code 0 even if there are errors.
  • –max-line-length: Set the maximum length that any line (with some exceptions) may be.
  • –extend-ignore: list of error codes to add to the list of ignored ones.
  • –statistics: Count the number of occurrences of each error/warning code and print a report.
- name: Lint with flake8 (before reformat)

    run: |

      flake8 . --exclude .github --count --exit-zero --max-line-length=120 --extend-ignore=BLK100 --statistics 2>&1 | tee flake8_before.txt

Step 5: Reformat code and sort imports alphabetically and by standard library, third-party and local.

You can use optional flags to customise the linting:

  • –skip-gitignore: Treat project as a git repository and ignore files listed in .gitignore.
  • –skip-glob: skip all files in a nested path.
  • –profile black: Make isort compatible with black.
- name: Reformat and sort imports

    run: |

      isort --skip-gitignore --skip-glob .github --profile black .

Step 6: Reformat code with black, a PEP8 compliant code formatter.

You can use optional flags to customize the linting:

  • –exclude: add files or directories to list to be excluded from checks.
  • –line-length: Change the default line length.
- name: Reformat with black

    run: |

      black --exclude '/.github/' --line-length 120 .

Step 7: Reformat code with autopep8, a PEP8 conformant code formatter.

You can use optional flags to customize the linting:

  • –exclude: list of files or directories to exclude from checks.
  • –in-place: Make changes to files in place.
  • –recursive: Run recursively over directories; must be used with –in-place or –diff.
  • –max-line-length: set maximum allowed line length.
- name: Reformat with autopep8

    run: |

      autopep8 --exclude .github --in-place --recursive --max-line-length 120 .

Step 8: Lint with flake 8 after reformatting the code and save the list of errors into a .TXT file.

You can use optional flags to customize the linting, like we described in step 4. The flake8_after.txt file can be used as a checklist to remove errors and improve code quality.

- name: Lint with flake8 (after reformat)

    run: |

      flake8 . --exclude .github --count --exit-zero --max-line-length=120 --extend-ignore=BLK100 --statistics 2>&1 | tee flake8_after.txt

Step 9: Commit the files to git. The data of the user that did the original push gets added automatically with the configuration below.

- name: Commit files

   continue-on-error: true

   run: |

    git config --global user.email ${{ github.actor }}

    git config --global user.name ${{ github.actor }}@users.noreply.github.com

    git add .

    git commit -m "Checking Coding Guidelines and Reformatting Code - $(date +'%Y-%m-%dT%H:%M:%S')"

Step 10: Push the commit to GitHub.

- name: Push changes

   uses: ad-m/github-push-action@master

   with:

    github_token: ${{ secrets.GITHUB_TOKEN }}

    branch: ${{ github.ref }}

Editor's Pick