Python Multi-Version Test Template#

This template triggers test executions across multiple Python versions using parallel jobs. Useful for libraries or projects that must ensure compatibility between different Python versions.

Usage#

In your project’s .gitlab-ci.yml:

include:
  - project: "bytecode-solutions/core/core-gitlab-templates"
    file: "tests/run_tests.yml"

Advanced Usage with Custom Command#

You can customize the test command to run:

run-tests:
  extends: .run_tests
  variables:
    cmd: "pytest tests/ -v --cov=mypackage"

Installing Additional Extras#

If your project requires optional dependency groups beyond [dev], pass them via the extras input:

include:
  - project: "bytecode-solutions/core/core-gitlab-templates"
    file: "templates/tests/run_tests.yml"
    ref: "release"
    inputs:
      extras: "postgres,mongo"

This installs .[postgres,mongo] on top of the base .[dev] install before running the test command.

Custom Python Versions#

Override the default Python versions:

run-tests:
  extends: .run_tests
  variables:
    versions:
      - PYTHON_IMAGE: "python:3.11"
      - PYTHON_IMAGE: "python:3.12"
      - PYTHON_IMAGE: "python:3.13"

Parameters#

The template defines the following inputs:

branch

Optional: Restrict execution to a specific branch. If empty, the tests will run on all branches.

Default: "" (runs on all branches)

stage

The pipeline stage in which the test job runs.

Default: tests

cmd

Command to execute to run the tests. This allows you to customize how tests are executed for your specific project.

Default: python manager.py run-coverage

extras

Optional: additional pip extras to install before running the tests. Accepts a comma-separated string of extra names (e.g. all, postgres,mongo). Installed as pip install ".[extras]" after the base [dev] install. When empty, no extra install step is run.

Default: "" (no extras installed)

versions

Python versions to test. Array of image specifications that determines which Python versions will be tested in parallel.

Default:
  • python:3.9

  • python:3.10

  • python:3.11

  • python:3.12

  • python:3.13

  • python:3.14

  • pypy:3.11

Behavior#

The job performs the following steps:

  1. PyPy Support: Automatically detects PyPy images and installs Rust toolchain (required for packages with Rust dependencies)

  2. Environment Setup: Creates a virtual environment for isolated testing

  3. Package Installation: Attempts to install the package with [dev] extras. If the [dev] extra doesn’t exist, falls back to installing the base package

  4. Extras Installation: If the extras input is non-empty, installs the specified extras (e.g. pip install ".[postgres,mongo]")

  5. Test Execution: Runs the specified test command

  1. Parallel Execution: Executes tests simultaneously across all specified Python versions using GitLab’s matrix feature

Special Features#

PyPy Support#

The template automatically detects when running on PyPy and installs the Rust toolchain. This is essential for packages that have dependencies with Rust extensions (like cryptography):

if [[ "$PYTHON_IMAGE" == "pypy:3.11" ]]; then
    apt install -y curl build-essential
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
    source "$HOME/.cargo/env"
fi

Flexible Installation#

The template gracefully handles projects that may or may not define development extras:

pip install ".[dev]" || pip install .

This ensures the job doesn’t fail if your pyproject.toml or setup.py doesn’t define a [dev] extra.

Extras Installation#

After the base install, the template conditionally installs any additional extras declared via the extras input:

if [[ -n "postgres,mongo" ]]; then
    pip install ".[postgres,mongo]"
fi

When extras is empty (the default), this step is skipped entirely.

Parallel Execution#

Tests run simultaneously across all Python versions using GitLab’s matrix feature, significantly reducing total pipeline execution time.

Pip Caching#

The template caches pip packages in .pip_cache to speed up subsequent runs.

Tip

Consider reducing the number of Python versions tested on feature branches and running the full matrix only on main branches or tags to save CI resources.

Example: Targeted Testing#

# Test only latest versions on feature branches
run-tests-quick:
  extends: .run_tests
  variables:
    versions:
      - PYTHON_IMAGE: "python:3.13"
  only:
    - branches
  except:
    - main

# Full test matrix on main branch
run-tests-full:
  extends: .run_tests
  only:
    - main
    - tags