How to setup CI for your Python project


What is CI?

Continuous Integration (CI) is a practice in software development that implements frequently running some checks on each commit into remote repository.

Benefits of CI:

  • detecting bugs and code inconsistency before pushing to main branch
  • possibility to build your project, run tests, linters, formatters on each iteration of the code on environment similar to production
  • possibility to test code before starting code review, which will allow automate some steps of code review, like checking code style
  • testing code frequently after each push to remote repository will reduce amount of bugs in production
  • all checks on CI are custom and defined individually per project/repository

As CI requires to build your code for running all actions, it requires a server.

There are few options:

  • self-hosted solutions
  • paid-for solutions
  • solutions which are free for open-source

In this article I would explain how to setup a simple CI for Python project with GitHub Actions.

How to setup GitHub Actions

Github Actions setup is very easy. Just go to Github to your repo and click on Actions tab, you will see this:

There are couple of options:

  • setup workflow yourself
  • use one of suggested options

Let's try to use Python application. You will be able to edit before creating an actual workflow. Let's not change anything for now and use as it is.

I removed couple of lines and renamed the job into CI:

# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Python application

on:
  push

jobs:
  CI:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python 3.8
      uses: actions/setup-python@v2
      with:
        python-version: 3.8
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install flake8 pytest
        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
    - name: Lint with flake8
      run: |
        # stop the build if there are Python syntax errors or undefined names
        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
    - name: Test with pytest
      run: |
        pytest

Let's add couple of things here:

  • run black check
  • run pylint
  • run mypy
  • run isort
  • check our documentation coverage with interrogate
  • add requirements.txt file to setup environment for CI

We will get this nice yaml after:

# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Python application

on:
  push

jobs:
  CI:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python 3.8
      uses: actions/setup-python@v2
      with:
        python-version: 3.8
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements/ci.txt
    - name: Lint with flake8
      run: |
        # stop the build if there are Python syntax errors or undefined names
        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
      continue-on-error: true
    - name: Test with pytest
      run: |
        pytest
      continue-on-error: true
    - name: Run doc coverage
      run: |
        interrogate exceptions logging
      continue-on-error: true
    - name: Run black
      run: |
        black --check exceptions logging
      continue-on-error: true
    - name: Run pylint
      run: |
        pylint exceptions logging
      continue-on-error: true
    - name: Run isort
      run: |
        isort --check-only --diff -rc exceptions logging
      continue-on-error: true

A real repo with all examples and setup you can find here

Enjoy your CI setup ;-)

Used sources and books to read

  1. Github Actions basics
  2. More examples can be found here
  3. Photo by Mark Duffel on Unsplash