Patrick's Software Blog

Add Badges to a Python Project in GitLab

Desk with the GitLab logo and a GitLab project summary shows badges for code coverage and build status.

Introduction

This blog post demonstrates how to add badges to a Python project on GitLab.

The "pipeline" and "coverage" badges are the two badges that I like to include for each Python project that I have on GitLab. They provide a quick overview of the status of the project.

This blog post assumes that the Python project is using GitLab CI (Continuous Integration) to run a job that calculates the test coverage, such as using the pytest-cov package.

Add the Badges

Start with a Python project in GitLab that doesn’t have any badges:

GitLab Project without Badges

In the left-hand navigation pane, scroll down to the bottom and select ‘Settings’ –> ‘General’:

GitLab: Settings - General View

Scroll down to the ‘Badges’ sections and click on ‘Expand’:

GitLab: Badges section

Add the ‘Pipeline Status’ badge:

GitLab: Pipeline Status Badge Data

The values to fill in to the three fields are:

  • Name: Pipeline Status
  • Link: https://gitlab.com/%{project_path}/-/commits/%{default_branch}
  • Badge image URL: https://gitlab.com/%{project_path}/badges/%{default_branch}/pipeline.svg

Add the ‘Test Coverage’ badge:

GitLab: Test Coverage Badge Data

The values to fill in to the three fields are:

  • Name: Test Coverage
  • Link: https://gitlab.com/%{project_path}/-/commits/%{default_branch}
  • Badge image URL: https://gitlab.com/%{project_path}/badges/%{default_branch}/coverage.svg

At the bottom of the ‘Badges’ section, you should now see that both badges have been added:

GitLab: Badge Status

However, the ‘Test Coverage’ badge is still reporting an "unknown" coverage...

GitLab CI Script

The ‘Test Coverage’ badge uses a regex (Regular Expression) to parse the results of running a test coverage job in the CI (Continuous Integration) pipeline. In the .gitlab-ci.yml file for your project, the job that runs your test coverage tool needs to updated to include a new coverage element:

coverage:
  stage: Test
  script:
  - python -m  pytest --cov-report term-missing --cov=project
  coverage: '/^TOTAL.+?(\d+\%)$/'

The last line is a regex to extract the total coverage from the last line in the ‘Coverage’ job that runs in the Gitlab CI pipeline:

Name                Stmts   Miss  Cover   Missing
-------------------------------------------------
bild/__init__.py        0      0   100%
bild/directory.py      47      1    98%   43
bild/file.py           88     14    84%   49, 77-82, 94-96, 102-103, 111-112
-------------------------------------------------
TOTAL                 135     15    89%

For more information about how to parse other coverage reports:

For reference, here is a typical GitLab CI script (.gitlab-ci.yml) that I use to run a static analysis tool (flake8), run all the tests (pytest), and collect the test coverage metrics (pytest-cov):

# Tagged releases of Python docker images can be found at:
# https://hub.docker.com/r/library/python/tags/
image: python:3.10

# Change pip's cache directory to be inside the project directory since we can
# only cache local items.
variables:
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

# Pip's cache doesn't store the python packages
# https://pip.pypa.io/en/stable/reference/pip_install/#caching
#
# If you want to also cache the installed packages, you have to install
# them in a virtualenv and cache it as well.
cache:
  paths:
    - .cache/pip
    - venv/

before_script:
  - python --version  # Print out python version for debugging
  - python3 -m venv venv
  - source venv/bin/activate
  - pip install -r requirements.txt

stages:
  - Static Analysis
  - Test

flake8:
  stage: Static Analysis
  script:
  - flake8 --max-line-length=150 --ignore=E266 *.py project/*.py project/*/routes.py

pytest:
  stage: Test
  script:
  - python -m pytest

coverage:
  stage: Test
  script:
  - python -m  pytest --cov-report term-missing --cov=project
  coverage: '/^TOTAL.+?(\d+\%)$/'

In order to have the regex change take effect, a new commit needs to be made to the default branch for the project:

$ git commit -m “Update GitLab CI script”
$ git push origin main

Wait for GitLab CI pipeline to run:

GitLab: Pipeline Status

Once the pipeline completes, you should see the badges working in the ‘Project Overview’ section:

GitLab: Badges

Conclusion

This blog post demonstrated the steps for adding badges to a Python project in GitLab.