Introduction

Today, I’m going to look at using tox, linters, and a GitLab CI/CD pipeline to check code quality before committing to a repository.

First – what is tox? Tox can be used to automate and standardize testing in Python. It is a virtualenv management and test command line tool you can use to run different tests for Python projects and also check the syntax of YAML and Ansible syntax as well, for example.

What are linters? Linters are programs that are used to check code quality. They can help to prevent bugs in a project, make code more readable, and make code cleaner and less complex.

There are many linters you can use. In this example, I will use a couple of different ones such as pylint, flake8, yamllint, and ansible-lint.

Once we have tox and the linters setup, we will use pre-commit Git hooks with GitLab CI/CD pipelines to automate the linting process on commit.

Procedure

  1. Set up a simple tox.ini file. This file will go in the same directory as your setup.py file.

    [tox]
    minversion = 3.8.0
    envlist = linters 
    skipsdist = True
    ignore_basepython_conflict = True
    skip_missing_interpreters = False
    requires =
     tox-extra 
    [gh-actions]python = 3.9: py39
    [testenv]
    usedevelop = True
    install_command = pip install -U {opts} {packages}
    [testenv:linters]
    deps = pre-commit>=1.21.0 
    pylint>=2.12.0 
    -r {toxinidir}/requirements.txt 
    -r {toxinidir}/test-requirements.txt
    commands = python -m pre_commit run -a
  2. Now you can run tox locally and see what errors are detected if any:

3. To set up the linter to run every time you commit code locally, install and configure pre-commit:

pip install pre-commit

4. Add a pre-commit configuration file and set stages to commit:

.pre-commit-config.yaml


---
default_language_version:
  python: python3
minimum_pre_commit_version: "1.14.0"
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.1.0
    hooks:
      - id: end-of-file-fixer
        stages: [commit]
      - id: trailing-whitespace
        stages: [commit]
      - id: mixed-line-ending
        stages: [commit]
      - id: check-byte-order-marker 
        stages: [commit]
      - id: check-executables-have-shebangs
        stages: [commit]
      - id: check-merge-conflict
        stages: [commit]
      - id: debug-statements
        stages: [commit]
      - id: check-json
        stages: [commit]
      - id: check-yaml
        stages: [commit]
        files: .*\.(yaml|yml)$
        args: ["--unsafe"]
  - repo: https://github.com/pre-commit/mirrors-isort
    rev: v5.1.4
    hooks:
      - id: isort
        stages: [commit]
  - repo: https://github.com/PyCQA/flake8.git
    rev: 3.8.3
    hooks:
      - id: flake8
        stages: [commit]
  - repo: local
    hooks:
      - id: pylint
        stages: [commit]
        name: pylint
        entry: pylint
        language: system
        types: [python]
        require_serial: true
        args: [
          "-j 4",
          "--reports=n", # Simple report 
          "--fail-under=9",
          "--output=pylint.report.txt", 
          "--output-format=text"
        ]
  - repo: https://github.com/adrienverge/yamllint.git
    rev: v1.24.2
    hooks:
      - id: yamllint
        stages: [commit]
        files: \.(yaml|yml)$
        types: [file, yaml]
        entry: yamllint --strict -f parsable
  - repo: https://github.com/ansible/ansible-lint.git
    rev: v5.3.2
    hooks:
      - id: ansible-lint
        stages: [commit]
        always_run: true
        pass_filenames: false
        verbose: true
        entry: bash -c "ANSIBLE_LIBRARY=plugins/modules ansible-lint --force-color -p -v" 
  - repo: https://github.com/openstack-dev/bashate.git
    rev: 2.0.0
    hooks:
      - id: bashate
        stages: [commit]
        entry: bashate --error . --ignore=E006,E040

5. Run pre-commit install to setup the git hook scripts:

pre-commit install

Now pre-commit will automatically run locally on git commit.

To run tox from a GitLab CI/CD, create a .gitlab-ci.yml file:

stages:
  - test

lint:
  images: python: 3.8
  stage: test
  tags:
    - shared # use the shared tag when using shared runners
  before_script:
    - pip install -r requirements.txt
    - pip install tox
  script:
    - tox -v -e linters

Test your pipeline now by committing code to your repo.

Conclusion

Now you can go ahead and add more tests or test multiple Python versions, etc.

Note: I came across this error when configuring tox to run in the CI/CD pipeline:

 × python setup.py develop did not run successfully.
    │ exit code: 1
    ╰─> [4 lines of output]
        running develop
        /builds/my_project/.tox/linters/lib/python3.8/site-packages/setuptools/command/easy_install.py:144: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.
          warnings.warn(
        error: error in 'egg_base' option: 'my_project' does not exist or is not a directory
        [end of output]
    
    note: This error originates from a subprocess, and is likely not a problem with pip.
error: subprocess-exited-with-error
× python setup.py develop did not run successfully.
│ exit code: 1
╰─> [4 lines of output]
    running develop
    /builds/my_project/.tox/linters/lib/python3.8/site-packages/setuptools/command/easy_install.py:144: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.
      warnings.warn(
    error: error in 'egg_base' option: 'my_project' does not exist or is not a directory
    [end of output]
note: This error originates from a subprocess, and is likely not a problem with pip.

The fix was to add add the following to my setup.py file:

from setuptools import find_packages, setup

setup(
    packages=find_packages(exclude=["tests.*", "tests"]),
)

Keep Learning!

This error message is only visible to WordPress admins

Error: No feed found.

Please go to the Instagram Feed settings page to create a feed.